//////////////////////////////////////////////////////
// 程序名称：绘制曲线
// 功 能：绘制抛物样条/Bezier/B样条曲线，并进行保存/读取（读目录操作仅能在x86下编译成功）
// 编译环境：Visual Studio 2017，EasyX_20190219(beta)
// 作 者：xyqlx<mxxyqlx@qq.com>
// 最后修改：2019-4-28
#include <conio.h>
#include <io.h>
#include <cstdio>
#include <vector>
#include <graphics.h>
#include <algorithm>
#include <list>
#include <string>
#include <ctime>
#include <iterator>
#include <fstream>
#include <sstream>
#include <cmath>

using std::list;
using std::string;

class Point
{
public:
	int x, y;
	Point(int x, int y) : x(x), y(y) {}
	bool operator==(const Point& rhs){
		return x == rhs.x && y == rhs.y;
	}
};
enum MouseEvent
{
	LeftClick,
	RightClick,
	ScrollUp,
	ScrollDown,
	MouseMove
};

class Curve
{
public:
	int pos = 0;
	list<Point> points;
	string type;
	list<Point>::iterator current;
	Curve(string type = "Curve") : type(type)
	{
		current = points.begin();
	}
	Curve(const Curve &rhs)
	{
		points = rhs.points;
		type = rhs.type;
		current = points.end();
		pos = 0;
	}
	const Curve &operator=(const Curve &rhs)
	{
		points = rhs.points;
		current = points.end();
		type = rhs.type;
		pos = 0;
		return *this;
	}
	virtual void DrawCurve() const
	{
		setlinecolor(MAGENTA);
		if (points.empty())
			return;
		else if (type == "ParSpl")
		{
			int x, y, i, j, n, k = 10;
			float t1, t2, t3, t, a, b, c, d;
			std::vector<Point> p;
			p.push_back(points.front());
			for (Point point : points)
			{
				p.push_back(point);
			}
			p.push_back(points.back());
			t = 0.5 / k;
			moveto(p[0].x, p[0].y);
			n = p.size();
			for (i = 0; i < n - 3; i++)
			{
				for (j = 1; j < k; j++)
				{
					t1 = j * t;
					t2 = t1 * t1;
					t3 = t2 * t1;
					a = -4.0 * t3 + 4.0 * t2 - t1;
					b = 12.0 * t3 - 10.0 * t2 + 1.0;
					c = -12.0 * t3 + 8.0 * t2 + t1;
					d = 4.0 * t3 - 2.0 * t2;
					x = a * p[i].x + b * p[i + 1].x + c * p[i + 2].x + d * p[i + 3].x;
					y = a * p[i].y + b * p[i + 1].y + c * p[i + 2].y + d * p[i + 3].y;
					lineto(x, y);
				}
				lineto(p[i + 2].x, p[i + 2].y);
			}
		}
		else if (type == "Bezier")
		{
			std::vector<Point> p;
			for (Point point : points)
			{
				p.push_back(point);
			}

			int dimersion = 2;
			int precision = 100;
			int number = p.size();

			if (number < 2)
				return;

			std::vector<int> mi(number);
			mi[0] = mi[1] = 1;
			for (int i = 3; i <= number; i++)
			{

				std::vector<int> t(i - 1);
				for (int j = 0; j < i - 1; j++)
				{
					t[j] = mi[j];
				}

				mi[0] = mi[i - 1] = 1;
				for (int j = 0; j < i - 2; j++)
				{
					mi[j + 1] = t[j] + t[j + 1];
				}
			}

			moveto(p[0].x, p[0].y);
			for (int i = 0; i < precision; i++)
			{
				float t = (float)i / precision;
				float tx = 0.0f, ty = 0.0f;
				for (int k = 0; k < number; k++)
				{
					tx += pow(1 - t, number - k - 1) * p[k].x * pow(t, k) * mi[k];
					ty += pow(1 - t, number - k - 1) * p[k].y * pow(t, k) * mi[k];
				}
				lineto(tx, ty);
			}
		}
		else if (type == "BSpline")
		{
			int x, y, i, j, n, k = 10;
			float t1, t2, t3, t, a, b, c, d;
			std::vector<Point> p;
			for (Point point : points)
			{
				p.push_back(point);
			}
			t = 1.0f / k;
			bool start = false;
			n = p.size();
			for (i = 0; i < n - 3; i++)
			{
				for (j = 1; j < k; j++)
				{
					t1 = j * t;
					t2 = t1 * t1;
					t3 = t2 * t1;
					a = (-t3 + 3.0f * t2 - 3.0f * t1 + 1.0f) / 6.0f;
					b = (3.0f * t3 - 6.0f * t2 + 4.0f) / 6.0f;
					c = (-3.0f * t3 + 3.0f * t2 + 3.0f * t1 + 1.0f) / 6.0f;
					d = 1.0f * t3 / 6.0f;
					x = a * p[i].x + b * p[i + 1].x + c * p[i + 2].x + d * p[i + 3].x;
					y = a * p[i].y + b * p[i + 1].y + c * p[i + 2].y + d * p[i + 3].y;
					if (start)
						lineto(x, y);
					else
					{
						start = true;
						moveto(x, y);
					}
				}
			}
		}
	}
	virtual string Dump() const
	{
		string data = type + ":";
		for (list<Point>::const_iterator it = points.begin(); it != points.end(); ++it)
		{
			data += " " + std::to_string(it->x) + " " + std::to_string(it->y);
		}
		return data;
	}
	void LoadDump(string &data)
	{
		std::stringstream ss(data);
		int x, y;
		ss >> type;
		type = type.substr(0, type.size() - 1);
		points.clear();
		while (ss >> x)
		{
			ss >> y;
			points.push_back(Point(x, y));
		}
		current = points.end();
	}
	void Redraw() const
	{
		DrawLine();
		DrawCurve();
	}
	void DrawLine() const
	{
		list<Point>::const_iterator it = points.begin();
		if (it == points.end())
			return;
		moveto(it->x, it->y);
		setlinecolor(LIGHTGRAY);
		for (++it; it != points.end(); ++it)
		{
			lineto(it->x, it->y);
		}
		setlinecolor(255 << ((pos + 1) * 8));
		if (current != points.end())
			circle(current->x, current->y, 10);
	}
	bool State(MouseEvent event, int x, int y)
	{
		//point = next(current)
		list<Point>::iterator point = current;
		if (point != points.end())
			++point;
		switch (event)
		{
		case LeftClick:
			if (current == points.end())
			{
				points.push_back(Point(x, y));
				current = points.begin();
				pos = 1;
			}
			else if (pos == -1)
			{
				current = points.insert(current, Point(x, y));
			}
			else if (pos == 1)
			{
				if (point == points.end())
				{
					points.push_back(Point(x, y));
					++current;
				}
				else
					current = points.insert(point, Point(x, y));
			}
			else
			{
				current->x = x;
				current->y = y;
			}
			break;
		case RightClick:
			if (current == points.end())
				break;
			if (pos == -1)
				pos = 1;
			if (pos == 1)
			{
				if (point != points.end())
				{
					while (point != points.end())
					{
						++point;
						++current;
					}
				}
				else
				{
					current = points.end();
					return 1;
				}
			}
			else
			{
				if (point == points.end())
				{
					if(current == points.begin())
						return 1;
					--current;
					points.pop_back();
				}
				else
					current = points.erase(current);
			}
			break;
		case MouseMove:
			break;
		case ScrollUp:
			if (pos != -1)
				--pos;
			else if (current != points.begin())
			{
				--current;
				pos = 0;
			}
			break;
		case ScrollDown:
			if (pos != 1)
				++pos;
			else if (point != points.end())
			{
				++current;
				pos = 0;
			}
			break;
		default:
			break;
		}
		return 0;
	}
};

//使用缓冲技术，减少因图形数量增加时出现的大量Redraw消耗
IMAGE img(640, 480);
//曲线列表，用于存储
list<Curve> curveList;

void Refresh()
{
	SetWorkingImage();
	putimage(0, 0, &img, SRCCOPY);
}

//初始化画布和屏幕
void Init()
{
	//填充背景为白色
	SetWorkingImage(&img);
	setbkcolor(WHITE);
	cleardevice();
	//画按钮（
	moveto(5, 5);
	settextcolor(BLACK);
	outtext(TEXT("Save      Load"));
	setlinecolor(BLACK);
	rectangle(0, 0, 60, 30);
	rectangle(60, 0, 120, 30);
	SetWorkingImage();
	putimage(0, 0, &img, SRCCOPY);
}
//保存所有曲线
void Save()
{
	closegraph();
	string filename;
	char buf[128];
	printf("Save");
	printf("Please input the file's name:\n");
	scanf_s("%128s", buf, _countof(buf));
	filename = buf;
	filename += ".crv";
	std::ofstream fout(filename.c_str());
	if (fout.is_open())
	{
		for (auto cv : curveList)
			fout << cv.Dump() << std::endl;
		fout.close();
		printf("succeed to save");
	}
	else
	{
		printf("falied to save");
	}
	initgraph(640, 480);
	Init();
	SetWorkingImage(&img);
	for (auto curve : curveList)
	{
		curve.Redraw();
	}
	SetWorkingImage();
	putimage(0, 0, &img, SRCCOPY);
}
//加载曲线
void Load()
{
	closegraph();
	_finddata_t fileInfo;
	int crvNum = 0;
	long handle = _findfirst("*.crv", &fileInfo);
	if (handle == -1L)
	{
		printf("Can't find *.crv files, you should move it in the same folder.\n");
		system("pause");
		return;
	}
	do
	{
		crvNum++;
		printf("%s\n", fileInfo.name);
	} while (_findnext(handle, &fileInfo) == 0);
	printf("Type the file's name without suffix.\nFor example, if the file is \"sample.crv\", you should input \"sample\":\n");
	string filename;
	char buf[128];
	scanf_s("%128s", buf, _countof(buf));
	filename = buf;
	filename += ".crv";
	std::ifstream fin(filename.c_str());
	if (!fin.is_open())
		printf("File Not Exists\n");
	else
	{
		string data;
		while (std::getline(fin, data))
		{
			Curve curve;
			curve.LoadDump(data);
			curveList.push_back(curve);
		}
	}
	initgraph(640, 480);
	//初始化
	Init();
	//开始绘制
	SetWorkingImage(&img);
	for (auto curve : curveList)
	{
		curve.Redraw();
	}
	SetWorkingImage();
	putimage(0, 0, &img, SRCCOPY);
}
//清理函数（虽然似乎不会执行）
void Clear()
{
}

string curveType = "ParSpl";

//主消息循环
void MessageLoop()
{
	MOUSEMSG msg;
	Curve *currentCurve = new Curve(curveType);

	//缺少说明，仅仅用于调试
	while (1)
	{
		msg = GetMouseMsg();
		switch (msg.uMsg)
		{
		case WM_MOUSEMOVE:
			break;
		case WM_LBUTTONDOWN:
			if (msg.x < 120 && msg.y < 30)
			{
				if (msg.x < 60)
					Save();
				else
					Load();
			}
			else
			{
				currentCurve->State(LeftClick, msg.x, msg.y);
				Refresh();
				currentCurve->Redraw();
			}
			break;
		case WM_RBUTTONDOWN:
			if (currentCurve->points.empty())
			{
				list<Curve>::iterator closestCurve = curveList.end();
				int mindist2 = 640 * 640 + 480 * 480;
				Point closetPoint(0, 0);
				for (auto it = curveList.begin(); it != curveList.end(); ++it)
				{
					for (auto point : it->points)
					{
						int temp = pow(msg.x - point.x, 2) + pow(msg.y - point.y, 2);
						if (mindist2 > temp)
						{
							mindist2 = temp;
							closestCurve = it;
							closetPoint = point;
						}
					}
				}
				if (closestCurve == curveList.end())
					break;
				Init();
				SetWorkingImage(&img);
				for (auto it = curveList.begin(); it != curveList.end(); ++it)
				{
					if (it == closestCurve)
						continue;
					it->Redraw();
				}
				SetWorkingImage();
				putimage(0, 0, &img, SRCCOPY);
				currentCurve = new Curve(*closestCurve);
				curveList.erase(closestCurve);
				currentCurve->current = std::find(currentCurve->points.begin(),currentCurve->points.end(),closetPoint);
				currentCurve->pos = 0;
				currentCurve->Redraw();
			}
			else if (currentCurve->State(RightClick, msg.x, msg.y))
			{
				SetWorkingImage(&img);
				currentCurve->Redraw();
				SetWorkingImage();
				curveList.push_back(*currentCurve);
				delete currentCurve;
				currentCurve = new Curve(curveType);
			}
			Refresh();
			currentCurve->Redraw();
			break;
		case WM_MOUSEWHEEL:
			Refresh();
			currentCurve->State(msg.wheel > 0 ? ScrollUp : ScrollDown, msg.x, msg.y);
			currentCurve->Redraw();
			break;
		}
	}
}

int main(int argc, char** argv)
{
	//处理传参
	if(argc > 1){
		string temp = argv[1];
		if(temp.length() > 1)
			curveType = temp;
	}
	// 创建绘图窗口，大小为 640x480 像素
	initgraph(640, 480);
	//初始化
	Init();
	//开始绘制
	MessageLoop();
	//结束绘制
	_getch();	 // 按任意键继续
	closegraph(); // 关闭绘图窗口
	Clear();
	return 0;
}